﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WpfBeyond
{
    /// <summary>
    /// 消息头
    /// </summary>
    class IpPacket_Head
    {
        public UInt16 tag = 0xFEFD;
        public UInt16 version = 0x100;
        public UInt32 SessionId = 0;
        public Byte type = 0;/*1:请求，2:应答*/
        public Byte sign = 0;/*是否签名*/
        public UInt16 length = 0;/*数据包长度*/
    }

    /*终端工作状态	1	终端当前所处工作状态。
    1：空闲，终端在线，但未进行任何广播操作；
    2：工作，终端进行广播中；
    3：故障，终端处于故障状态。
    首次注册标识	1	终端通电启动后第一次注册标识。
    1：首次注册；2：非首次注册。
    物理地址编码长度	1	物理地址编码的长度。
    物理地址编码	物理地址编码长度	ASCII编码，终端唯一标识，出厂时生成固定不变。*/
    class IpPacket_Heartbeat
    {
        public Byte status = 0;
        public Byte firstReg = 0;
        public Byte sn_len = 0;
        public string sn = "";
    }

    //请求播放接口
    class IpPacket_Play
    {
        public byte[] ebmid = new byte[18] { 0xF5, 0x34, 0x12, 0x82, 0x10, 0x10, 0x00, 0x03, 0x14, 0x01, 0x02, 0x91, 0x20, 0x21, 0x07, 0x25, 0x00, 0x09 };//应急广播消息编码
        //1：应急演练-发布系统演练
        //2：应急演练-模拟演练
        //3：应急演练-实际演练
        //4：应急广播
        //5：日常广播
        public Byte bc_type = 0;//广播类型

        //事件级别
        public Byte event_level = 0;
        //事件类型
        public byte[] event_type = new byte[5];
        public byte volume = 0;//音量

        public byte[] begin_time = new byte[4];//UTC时间
        public byte[] end_time = new byte[4];//UTC时间

        public byte aux_num = 0;//辅助数据数量

        public byte aux_type = 0;
        public ushort aux_len = 0;
        public byte[] aux_content;

        public IpPacket_Play()
        {
            
        }
    }

    class IpPacket_Stop
    {
        public byte[] ebmid = new byte[18] { 0xF5, 0x34, 0x12, 0x82, 0x10, 0x10, 0x00, 0x03, 0x14, 0x01, 0x02, 0x91, 0x20, 0x21, 0x07, 0x25, 0x00, 0x72 };//应急广播消息编码
    }

    class param_info
    {
        public Byte Tag;//标识
        public Byte len;
        public byte[] content;
    }

    class IpPacket_Query
    {
        public Byte param_num;//查询参数个数
        public List<Byte> param_tag_list = new List<Byte>();
    }

    class IpPacket_Set
    {
        public Byte param_num;
        public List<param_info> param_list = new List<param_info>();
    }


    /*通用应答
     结果代码	1	0标识成功，其他参见错误代码列表。
结果描述长度	2	结果描述的长度；
如果无结果描述则长度为0。
	其它		可选，对应答结果进行描述。采用ASCII码字符串格式。也可以由厂家自定义（如：区域树是否更新/区域播发状态是否有变化）
*/
    class IpPacket_normal
    {
        public Byte code = 0;
        public UInt16 len = 0;
        public string msg = "";        
    }

    class IpPacket_Param
    {
        public Byte sn_length = 0;
        public string sn = "";
        public UInt16 param_len = 0;
        public string param = "";
    }

    /// <summary>
    /// 消息体
    /// </summary>
    class IpPacket_Body
    {
        public byte[] src_obj;//源对象编码12位
        public string sn_src;
        public UInt16 dst_num;
        public byte[] dst_obj;//目的对象编码序列，12*数据目标对象数量
        public List<string> sn_dst = new List<string>();
        public Byte type;
        public UInt16 length;

        /*1	序列号长度	1	序列号长度。
        2	序列号	序列号长度	ASCII编码，终端唯一标识，出厂时生成，固定不变。
        3	参数长度	2	
        4	参数内容	参数长度	JSON格式内容，JSON参数参考设备CGI格式。
        */
        public IpPacket_Heartbeat p_heartbeat;
        public IpPacket_Play p_play;
        public IpPacket_Stop p_stop;

        public IpPacket_Query p_query;
        public IpPacket_Set p_set;

        public IpPacket_normal p_normal;
    }

    /// <summary>
    /// 验证数据
    /// </summary>
    class IpPacket_Verify
    {
        public UInt16 length;
        public UInt32 utc;
        public string sn;
        public string data;
        public UInt32 crc;
    }

    /// <summary>
    /// IP广播数据包
    /// </summary>
    class IpPacket
    {
        public IpPacket_Head head = null;
        public IpPacket_Body body = null;
        public IpPacket_Verify verify = null;

        public bool Verified = false;

        public byte[] data = new byte[20 * 1024];
        public int data_len = 0;

        public byte code = 0;
        public string msg = "";

        /// <summary>
        /// 请求包
        /// </summary>
        /// <param name="type">包类型</param>
        public IpPacket(byte type)
        {
            head = new IpPacket_Head();

            body = new IpPacket_Body();
            body.type = type;
            /*终端心跳*/
            if (body.type == 0x10)
            {
                body.p_heartbeat = new IpPacket_Heartbeat();
            }
            else if (body.type == 0x1)
            {
                body.p_play = new IpPacket_Play();
            }
            else if (body.type == 0x2)
            {
                body.p_stop = new IpPacket_Stop();
            }
            else if (body.type == 0x11)//查询
            {
                body.p_query = new IpPacket_Query();
            }
            else if (body.type == 0x12)
            {
                body.p_set = new IpPacket_Set();
            }

            verify = new IpPacket_Verify();
        }

        /// <summary>
        /// 接收包
        /// </summary>
        public IpPacket()
        {
            head = new IpPacket_Head();
            body = new IpPacket_Body();
            verify = new IpPacket_Verify();
        }

        /// <summary>
        /// 解析接收的包
        /// </summary>
        /// <param name="data">接收数据</param>
        /// <returns></returns>
        public bool Parse(byte[] data)
        {
            int pos = 0;
            int i, j = 0;

            /*1	包头标记	2	值为0xFEFD。
            2	协议版本号	2	值为0x0200，当前协议采用的版本号，每次发布修定版本需要加1。
            3	会话标识	4	请求数据包在发送端的统一编号，单向递增。请求与应答的会话标识要保持一致。
            4	数据包类型	1	1：请求数据包；2：应答数据包。
            5	签名标识	1	0：不签名；1：验证数据包含对数据包消息体的数据签名。
            6	数据包长度	2	标识为整个应急广播数据包的长度。
            */
            head.tag = (UInt16)(data[0] * 256 + data[1]);

            if (head.tag != 0xFEFD) return false;

            head.version = (UInt16)(data[2] * 256 + data[3]);

            head.SessionId = (UInt32)(data[4] * 256 * 256 * 256 + data[5] * 256 * 256 + data[6] * 256 + data[7]);

            head.type = data[8];
            head.sign = data[9];

            head.length = (UInt16)(data[10] * 256 + data[11]);

            /*根据长度计算CRC*/
            UInt32 crc32 = CRC32_MPEG.GetCRC32(data, head.length - 4);
            verify.crc = (UInt32)(data[head.length - 4] * 256 * 256 * 256 + data[head.length - 3] * 256 * 256 + data[head.length - 2] * 256 + data[head.length - 1]);

            if (crc32 == verify.crc)
            {
                Verified = true;
            }
            else
            {
                return false;
            }

            /*1	数据源对象编码	9	数据包发送端的资源编码。
            应急广播资源编码采用18位数字码：
            资源类型码（2个数字，固定0612）+序列号7位（序列号位13个BCD，最后一位补0），采用压缩性BCD编码，9字节。
            2	数据目标对象数量	2	数据目标对象数量。
            3	数据目标对象编码序列	9*数据目标对象数量	数据包接收端的资源编码。
            目标对象编码格式同源对象编码。
            4	业务数据类型	
            请求包与应答包对应业务数据类型要保持一致。
            5	业务数据长度	2	业务数据内容长度。
            6	业务数据内容	业务数据长度	业务数据内容根据业务数据类型不同。
            */
            StringBuilder sb = new StringBuilder();
            pos = 12;
            body.src_obj = new byte[12];
            for (i = 0; i < 12; i++)
            {
                body.src_obj[i] = data[pos++];
            }

            //wangm:2020-6-3 心跳有些带DST_NUM=1,有些DST_NUM=0，注意，这里只是跳过，不解析
            body.dst_num = (UInt16)(data[pos] * 256 + data[pos + 1]);
            pos = pos + 2;

            body.dst_obj = new byte[body.dst_num * 12];
            for (j = 0; j < body.dst_num * 12; j++)
            {
                body.dst_obj[j] = data[pos++];
            }

            body.type = data[pos];
            pos++;

            body.length = (UInt16)(data[pos] * 256 + data[pos + 1]);
            pos = pos + 2;

            if (head.type == 2) //应答数据包
            {
                body.p_normal = new IpPacket_normal();

                body.p_normal.code = data[pos++];
                body.p_normal.len = (UInt16)(data[pos] * 256 + data[pos + 1]);
                pos += 2;

                body.p_normal.msg = ASCIIEncoding.UTF8.GetString(data, pos, body.p_normal.len);
                pos = pos + body.p_normal.len;
            }
            else //请求数据包
            {
                /*终端心跳*/
                if (body.type == 0x10)
                {
                    body.p_heartbeat = new IpPacket_Heartbeat();

                    body.p_heartbeat.status = data[pos++];
                    body.p_heartbeat.firstReg = data[pos++];
                    body.p_heartbeat.sn_len = data[pos++];

                    //BCD编码
                    sb.Remove(0, sb.Length);
                    for (i = 0; i < body.p_heartbeat.sn_len; i++)
                    {
                        sb.Append(data[pos + i].ToString("X2"));
                    }
                    body.p_heartbeat.sn = sb.ToString();
                    //body.p_heartbeat.sn = ASCIIEncoding.Default.GetString(data, pos, body.p_heartbeat.sn_len);

                    pos = pos + body.p_heartbeat.sn_len;

                    //ParseReback(data, (int)body.p_heartbeat.ext_len, pos);
                }
            }

            return true;
        }

        //打包回馈数据
        public void packet()
        {
            int i = 0;
            int pos = 0;

            data[pos++] = 0xfe;
            data[pos++] = 0xfd;

            data[pos++] = (byte)(head.version >> 8);
            data[pos++] = (byte)(head.version & 0xFF);

            data[pos++] = (byte)((head.SessionId & 0xFF000000) >> 24);
            data[pos++] = (byte)((head.SessionId & 0xFF0000) >> 16);
            data[pos++] = (byte)((head.SessionId & 0xFF00) >> 8);
            data[pos++] = (byte)(head.SessionId & 0xFF);

            data[pos++] = 2;//应答数据包
            data[pos++] = (byte)head.sign;

            //预留长度
            pos += 2;

            for (i = 0; i < 12; i++)
            {
                data[pos++] = body.dst_obj[i];
            }

            data[pos++] = 0x00;
            data[pos++] = (byte)(body.dst_num & 0xFF);

            for (i = 0; i < 12; i++)
            {
                data[pos++] = body.src_obj[i];
            }

            data[pos++] = body.type;

            int len_pos = pos;
            pos += 2;

            if (body.type == 0x10)
            {
                data[pos++] = 0; //结果代码
                data[pos++] = 0; //结果描述长度
                data[pos++] = 0;
            }
            else if( body.type == 0x11)
            {

            }

            data[len_pos] = (byte)(((pos - len_pos - 2) & 0xFF00) >> 8);
            data[len_pos + 1] = (byte)((pos - len_pos - 2) & 0xFF);

            //签名长度
            data[pos++] = 0;
            data[pos++] = 0;

            data[10] = (byte)(((pos + 4) & 0xFF00) >> 8);
            data[11] = (byte)((pos + 4) & 0xFF);

            UInt32 crc32 = CRC32_MPEG.GetCRC32(data, pos);
            data[pos++] = (byte)((crc32 & 0xFF000000) >> 24);
            data[pos++] = (byte)((crc32 & 0xFF0000) >> 16);
            data[pos++] = (byte)((crc32 & 0xFF00) >> 8);
            data[pos++] = (byte)(crc32 & 0xFF);

            data_len = pos;
        }

        public void encode()
        {
            int i = 0;
            int pos = 0;

            data[pos++] = 0xfe;
            data[pos++] = 0xfd;

            data[pos++] = (byte)(head.version >> 8);
            data[pos++] = (byte)(head.version & 0xFF);

            data[pos++] = (byte)((head.SessionId & 0xFF000000) >> 24);
            data[pos++] = (byte)((head.SessionId & 0xFF0000) >> 16);
            data[pos++] = (byte)((head.SessionId & 0xFF00) >> 8);
            data[pos++] = (byte)(head.SessionId & 0xFF);

            data[pos++] = 1;//请求数据包
            data[pos++] = (byte)head.sign;

            //预留长度
            pos += 2;

            for (i = 0; i < 12; i++)
            {
                data[pos++] = 0;// body.dst_obj[i];
            }

            data[pos++] = 0x00;
            data[pos++] = (byte)(1 & 0xFF);

            for (i = 0; i < 12; i++)
            {
                if (body.dst_obj != null)
                {
                    data[pos++] = body.dst_obj[i];
                }
                else
                {
                    data[pos++] = 0;
                }
            }

            data[pos++] = body.type;

            int len_pos = pos;
            pos += 2;

            if (body.type == 0x01)
            {
                for (i = 0; i < 18; i++)
                {
                    data[pos++] = body.p_play.ebmid[i];
                }

                data[pos++] = body.p_play.bc_type;
                data[pos++] = body.p_play.event_level;

                for (i = 0; i < 5; i++)
                {
                    data[pos++] = body.p_play.event_type[i];
                }

                data[pos++] = body.p_play.volume;

                for (i = 0; i < 4; i++)
                {
                    data[pos++] = body.p_play.begin_time[i];
                }

                for (i = 0; i < 4; i++)
                {
                    data[pos++] = body.p_play.end_time[i];
                }

                data[pos++] = body.p_play.aux_num;

                data[pos++] = body.p_play.aux_type;

                data[pos++] = (byte)((body.p_play.aux_len & 0xFF00) >> 8);
                data[pos++] = (byte)(body.p_play.aux_len);

                for (i = 0; i < body.p_play.aux_len; i++)
                {
                    data[pos++] = body.p_play.aux_content[i];
                }
            }
            else if (body.type == 0x02)
            {
                for (i = 0; i < 18; i++)
                {
                    data[pos++] = body.p_stop.ebmid[i];
                }
            }
            else if (body.type == 0x11)
            {
                data[pos++] = body.p_query.param_num;
                for (i = 0; i < body.p_query.param_num; i++)
                {
                    data[pos++] = body.p_query.param_tag_list[i];
                }
            }
            else if( body.type == 0x12)
            {
                data[pos++] = body.p_set.param_num;
                for (i = 0; i < body.p_set.param_num; i++)
                {
                    foreach(param_info param in body.p_set.param_list)
                    {
                        data[pos++] = param.Tag;
                        data[pos++] = param.len;

                        Array.Copy(param.content, 0, data, pos, param.len);
                        pos += param.len;
                    }
                }
            }

            data[len_pos] = (byte)(((pos - len_pos - 2) & 0xFF00) >> 8);
            data[len_pos + 1] = (byte)((pos - len_pos - 2) & 0xFF);

            //签名长度
            data[pos++] = 0;
            data[pos++] = 0;

            data[10] = (byte)(((pos + 4) & 0xFF00) >> 8);
            data[11] = (byte)((pos + 4) & 0xFF);

            UInt32 crc32 = CRC32_MPEG.GetCRC32(data, pos);
            data[pos++] = (byte)((crc32 & 0xFF000000) >> 24);
            data[pos++] = (byte)((crc32 & 0xFF0000) >> 16);
            data[pos++] = (byte)((crc32 & 0xFF00) >> 8);
            data[pos++] = (byte)(crc32 & 0xFF);

            data_len = pos;
        }


        public void Response(byte code, string msg)
        {
            int i = 0;
            int pos = 0;

            data[pos++] = 0xfe;
            data[pos++] = 0xfd;

            data[pos++] = 0x02;
            data[pos++] = 0;

            data[pos++] = (byte)((head.SessionId & 0xFF000000) >> 24);
            data[pos++] = (byte)((head.SessionId & 0xFF0000) >> 16);
            data[pos++] = (byte)((head.SessionId & 0xFF00) >> 8);
            data[pos++] = (byte)(head.SessionId & 0xFF);

            data[pos++] = 2;/*1：请求数据包；2：应答数据包。*/
            data[pos++] = 0;

            //预留长度
            pos += 2;

            for (i = 0; i < 9; i++)
            {
                data[pos++] = 0;
            }

            data[pos++] = 0x00;
            data[pos++] = 0x01;

            for (i = 0; i < 9; i++)
            {
                data[pos++] = 0;
            }

            data[pos++] = body.type;

            int len_pos = pos;
            pos += 2;

            //
            data[pos++] = code;

            byte[] json_byte = System.Text.Encoding.UTF8.GetBytes(msg);

            data[pos++] = (byte)(((json_byte.Length) & 0xFF00) >> 8);
            data[pos++] = (byte)((json_byte.Length) & 0xFF);

            Array.Copy(json_byte, 0, data, pos, json_byte.Length);
            pos += json_byte.Length;

            data[len_pos] = (byte)(((pos - len_pos - 2) & 0xFF00) >> 8);
            data[len_pos + 1] = (byte)((pos - len_pos - 2) & 0xFF);

            //签名长度
            data[pos++] = 0;
            data[pos++] = 0;

            data[10] = (byte)(((pos + 4) & 0xFF00) >> 8);
            data[11] = (byte)((pos + 4) & 0xFF);

            UInt32 crc32 = CRC32_MPEG.GetCRC32(data, pos);
            data[pos++] = (byte)((crc32 & 0xFF000000) >> 24);
            data[pos++] = (byte)((crc32 & 0xFF0000) >> 16);
            data[pos++] = (byte)((crc32 & 0xFF00) >> 8);
            data[pos++] = (byte)(crc32 & 0xFF);

            data_len = pos;
        }


    }
}
